﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Web;

namespace Srk.BetaseriesApi.Clients {

    /// <summary>
    /// This is a base partial Betaseries client implementation fitted for HTTP data transfert.
    /// This is to be subclassed and overriden.
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
    public partial class BetaseriesBaseHttpClient : IBetaseriesApi, IDisposable {

        #region Properties

        /// <summary>
        /// Base HTTP url for queries. 
        /// This will permit to use a different base adresse (for HTTPS, different port or domain name...).
        /// Default is http://api.betaseries.com/.
        /// </summary>
        /// <remarks>
        /// Value must be setted from .ctor.
        /// </remarks>
        protected readonly string BaseUrl;

        /// <summary>
        /// Formating string for query string.
        /// Must be set from sub-class.
        /// </summary>
        protected readonly string UrlFormat;

        #region IBetaseriesBaseApi Members

        public string UserAgent {
            get { return _userAgent; }
            set { _userAgent = value; }
        }
        private string _userAgent;

        public string Key {
            get { return _key; }
            set { _key = value; }
        }
        private string _key;

        public string SessionToken {
            get { return _sessionToken; }
        }
        private string _sessionToken;

        public string SessionUsername {
            get { return _sessionUsername; }
        }
        private string _sessionUsername;

        public void SetSessionTokens(string newSessionToken, string newSessionUsername) {
            _sessionToken = newSessionToken;
            _sessionUsername = newSessionUsername;

            if (factory != null) {
                factory.RaiseSessionTokenChanged(this, _sessionToken, _sessionUsername);
            }
        }

        #endregion

        #endregion

        #region .ctor

        protected BetaseriesBaseHttpClient(string urlFormat, string apiKey) : this(urlFormat, apiKey, null, null) { }

        protected BetaseriesBaseHttpClient(string urlFormat, string apiKey, string userAgent) : this(urlFormat, apiKey, userAgent, null) { }

        /// <summary>
        /// Mandatory .ctor
        /// </summary>
        /// <param name="apiKey">Mandatory field. Ask for your key on the website.</param>
        /// <param name="userAgent"></param>
        /// <param name="baseUrl">If you want to override urls</param>
        /// <param name="urlFormat"></param>
        protected BetaseriesBaseHttpClient(string urlFormat, string apiKey, string userAgent, string baseUrl) {
            if (string.IsNullOrEmpty(apiKey))
                throw new ArgumentException("Missing API Key", "apiKey");
            Key = apiKey;
            UserAgent = userAgent ?? "unknown-client";
            BaseUrl = baseUrl ?? "http://api.betaseries.com/";
            UrlFormat = urlFormat;
        }

        #endregion

        #region HTTP Query

        /// <summary>
        /// Execute an HTTP GET request through an HTTP wrapper.
        /// </summary>
        /// <param name="action">Service action</param>
        /// <param name="keyValues">Pairs of query string parameters (key1, value1, key2, value2...)</param>
        /// <returns>HTTP response body as a string.</returns>
        protected virtual string ExecuteQuery(string action, params string[] keyValues) {
            return ExecuteQuery(action, null, keyValues);
        }

        /// <summary>
        /// Execute an HTTP GET request through an HTTP wrapper.
        /// </summary>
        /// <param name="action">Service action</param>
        /// <param name="parameters">Query string parameters</param>
        /// <returns>HTTP response body as a string.</returns>
        protected virtual string ExecuteQuery(string action, Dictionary<string, string> parameters) {
            return ExecuteQuery(action, null, parameters);
        }

        /// <summary>
        /// Execute an HTTP GET/POST request through an HTTP wrapper.
        /// Request will use a POST method if postParameters contains stuff.
        /// </summary>
        /// <param name="action">Service action</param>
        /// <param name="postParameters">POST data parameters (pass null for GET method)</param>
        /// <param name="keyValues">Pairs of query string parameters (key1, value1, key2, value2...)</param>
        /// <returns>HTTP response body as a string.</returns>
        protected virtual string ExecuteQuery(string action, Dictionary<string, string> postParameters, params string[] keyValues) {
            if (keyValues.Length % 2 != 0)
                throw new ArgumentException("Invalid parameters count", "keyvalues");
            Dictionary<string, string> parameters = null;
            if (keyValues.Length > 0) {
                parameters = new Dictionary<string, string>();
                bool isKey = true;
                string key = null;
                foreach (var item in keyValues) {
                    if (isKey) {
                        key = item;
                    } else {
                        parameters.Add(key, item);
                    }
                    isKey = !isKey;
                }
            }
            return ExecuteQuery(action, postParameters, parameters);
        }

        /// <summary>
        /// Execute an HTTP GET/POST request through an HTTP wrapper.
        /// Request will use a POST method if postParameters contains stuff.
        /// </summary>
        /// <param name="action">Service action</param>
        /// <param name="parameters">Query string parameters</param>
        /// <param name="postParameters">POST data parameters (pass null for GET method)</param>
        /// <returns>HTTP response body as a string.</returns>
        protected virtual string ExecuteQuery(string action, Dictionary<string, string> postParameters, Dictionary<string, string> parameters) {
            parameters = parameters ?? new Dictionary<string, string>();
            parameters["key"] = Key;
            if (SessionToken != null) {
                parameters["token"] = SessionToken;
            }
            if (postParameters != null && postParameters.Count > 0)
                return http.ExecuteQuery(action, parameters, postParameters);
            else
                return http.ExecuteQuery(action, parameters);
        }

        #endregion

#pragma warning disable 1591
        #region IBetaseriesApi Members

        public virtual IList<Show> SearchShows(string title) {
            throw new NotSupportedException();
        }

        public virtual Show GetShow(string url) {
            throw new NotSupportedException();
        }

        public virtual IList<Show> GetShows() {
            throw new NotSupportedException();
        }

        public virtual IList<Episode> GetEpisodes(string showUrl) {
            throw new NotSupportedException();
        }

        public virtual IList<Episode> GetEpisodes(string showUrl, uint season) {
            throw new NotSupportedException();
        }

        public virtual Episode GetEpisode(string showUrl, uint season, uint episode) {
            throw new NotSupportedException();
        }

        public virtual void AddShow(string showUrl) {
            throw new NotSupportedException();
        }

        public virtual void RemoveShow(string showUrl) {
            throw new NotSupportedException();
        }

        public virtual IList<Subtitle> GetShowSubtitles(string showUrl, string language, uint? season, uint? episode) {
            throw new NotSupportedException();
        }

        public virtual IList<Subtitle> GetLatestSubtitles() {
            throw new NotSupportedException();
        }

        public virtual IList<Subtitle> GetLatestSubtitles(string language) {
            throw new NotSupportedException();
        }

        public virtual IList<Subtitle> GetLatestSubtitles(string language, uint number) {
            throw new NotSupportedException();
        }

        public virtual IList<Subtitle> GetLatestShowSubtitles(string showUrl) {
            throw new NotSupportedException();
        }

        public virtual IList<Subtitle> GetLatestShowSubtitles(string showUrl, string language) {
            throw new NotSupportedException();
        }

        public virtual IList<Subtitle> GetLatestShowSubtitles(string showUrl, string language, uint number) {
            throw new NotSupportedException();
        }

        public virtual IList<Episode> GetPlanning() {
            throw new NotSupportedException();
        }

        public virtual IList<Episode> GetMembersPlanning(string username) {
            throw new NotSupportedException();
        }

        public virtual IList<Episode> GetMembersPlanning(string username, bool unseenOnly) {
            throw new NotSupportedException();
        }

        public virtual string Authenticate(string username, string password) {
            throw new NotSupportedException();
        }

        public virtual bool GetIsSessionActive() {
            throw new NotSupportedException();
        }

        public virtual void Logoff() {
            throw new NotSupportedException();
        }

        public virtual Member GetMember(string username) {
            throw new NotSupportedException();
        }

        public virtual IList<Episode> GetMembersNextEpisodes(bool onlyNextEpisode) {
            throw new NotSupportedException();
        }

        public virtual Episode GetMembersNextShowEpisode(bool onlyNextEpisode, string showUrl) {
            throw new NotSupportedException();
        }

        public virtual void SetEpisodeAsSeen(string showUrl, uint season, uint episode, ushort? mark) {
            throw new NotSupportedException();
        }

        public virtual void SetEpisodeAsDownloaded(string showUrl, uint season, uint episode) {
            throw new NotSupportedException();
        }

        public virtual void SetEpisodeMark(string showUrl, uint season, uint episode, ushort mark) {
            throw new NotSupportedException();
        }

        public virtual IList<Notification> GetNotifications(bool? seen, uint? count, uint? fromNotificationId) {
            throw new NotSupportedException();
        }

        public virtual void Signup(string username, string password, string email) {
            throw new NotSupportedException();
        }

        public virtual IList<string> GetFriends(string username) {
            throw new NotSupportedException();
        }

        public virtual string[] GetBadges(string username) {
            throw new NotSupportedException();
        }

        public virtual IList<Comment> GetCommentsForShow(string showUrl) {
            throw new NotSupportedException();
        }

        public virtual IList<Comment> GetCommentsForEpisode(string showUrl, uint season, uint episode) {
            throw new NotSupportedException();
        }

        public virtual IList<Comment> GetCommentsForMember(string username) {
            throw new NotSupportedException();
        }

        public virtual void CommentShow(string showUrl, string comment, uint? inReplyTo) {
            throw new NotSupportedException();
        }

        public virtual void CommentEpisode(string showUrl, uint season, uint episode, string comment, uint? inReplyTo) {
            throw new NotSupportedException();
        }

        public virtual void CommentMember(string username, string comment, uint? inReplyTo) {
            throw new NotSupportedException();
        }

        public virtual IList<TimelineItem> GetMainTimeline(uint? count) {
            throw new NotSupportedException();
        }

        public virtual IList<TimelineItem> GetFriendsTimeline(uint? count) {
            throw new NotSupportedException();
        }

        public virtual IList<TimelineItem> GetMemberTimeline(string username, uint? count) {
            throw new NotSupportedException();
        }

        public virtual ApiStatus GetStatus() {
            throw new NotSupportedException();
        }

        #endregion

        #region IBetaseriesAsyncApi Members

        public void SearchShowsAsync(string title) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = SearchShows(title);
                    if (SearchShowsEnded != null) {
                        SearchShowsEnded(this, new AsyncResponseArgs<IList<Show>>(result));
                    }
                } catch (Exception ex) {
                    if (SearchShowsEnded != null) {
                        SearchShowsEnded(this, new AsyncResponseArgs<IList<Show>>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<IList<Show>> SearchShowsEnded;

        public void GetShowAsync(string url) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetShow(url);
                    if (GetShowEnded != null) {
                        GetShowEnded(this, new AsyncResponseArgs<Show>(result));
                    }
                } catch (Exception ex) {
                    if (GetShowEnded != null) {
                        GetShowEnded(this, new AsyncResponseArgs<Show>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<Show> GetShowEnded;

        public void GetShowsAsync() {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetShows();
                    if (GetShowsEnded != null) {
                        GetShowsEnded(this, new AsyncResponseArgs<IList<Show>>(result));
                    }
                } catch (Exception ex) {
                    if (GetShowsEnded != null) {
                        GetShowsEnded(this, new AsyncResponseArgs<IList<Show>>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<IList<Show>> GetShowsEnded;

        public void GetEpisodesAsync(string showUrl) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetEpisodes(showUrl);
                    if (GetEpisodesEnded != null) {
                        GetEpisodesEnded(this, new AsyncResponseArgs<IList<Episode>>(result));
                    }
                } catch (Exception ex) {
                    if (GetEpisodesEnded != null) {
                        GetEpisodesEnded(this, new AsyncResponseArgs<IList<Episode>>(ex));
                    }
                }
            });
        }

        public void GetEpisodesAsync(string showUrl, uint season) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetEpisodes(showUrl, season);
                    if (GetEpisodesEnded != null) {
                        GetEpisodesEnded(this, new AsyncResponseArgs<IList<Episode>>(result));
                    }
                } catch (Exception ex) {
                    if (GetEpisodesEnded != null) {
                        GetEpisodesEnded(this, new AsyncResponseArgs<IList<Episode>>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<IList<Episode>> GetEpisodesEnded;


        public void GetEpisodeAsync(string showUrl, uint season, uint episode) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetEpisode(showUrl, season, episode);
                    if (GetEpisodeEnded != null) {
                        GetEpisodeEnded(this, new AsyncResponseArgs<Episode>(result));
                    }
                } catch (Exception ex) {
                    if (GetEpisodeEnded != null) {
                        GetEpisodeEnded(this, new AsyncResponseArgs<Episode>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<Episode> GetEpisodeEnded;


        public void AddShowAsync(string showUrl) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    AddShow(showUrl);
                    if (AddShowEnded != null) {
                        AddShowEnded(this, new AsyncResponseArgs(true));
                    }
                } catch (Exception ex) {
                    if (AddShowEnded != null) {
                        AddShowEnded(this, new AsyncResponseArgs(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler AddShowEnded;

        public void RemoveShowAsync(string showUrl) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    RemoveShow(showUrl);
                    if (RemoveShowEnded != null) {
                        RemoveShowEnded(this, new AsyncResponseArgs(true));
                    }
                } catch (Exception ex) {
                    if (RemoveShowEnded != null) {
                        RemoveShowEnded(this, new AsyncResponseArgs(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler RemoveShowEnded;

        public void GetShowSubtitlesAsync(string showUrl, string language, uint? season, uint? episode) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetShowSubtitles(showUrl, language, season, episode);
                    if (GetShowSubtitlesEnded != null) {
                        GetShowSubtitlesEnded(this, new AsyncResponseArgs<IList<Subtitle>>(result));
                    }
                } catch (Exception ex) {
                    if (GetShowSubtitlesEnded != null) {
                        GetShowSubtitlesEnded(this, new AsyncResponseArgs<IList<Subtitle>>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<IList<Subtitle>> GetShowSubtitlesEnded;

        public void GetLatestSubtitlesAsync() {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetLatestSubtitles();
                    if (GetLatestSubtitlesEnded != null) {
                        GetLatestSubtitlesEnded(this, new AsyncResponseArgs<IList<Subtitle>>(result));
                    }
                } catch (Exception ex) {
                    if (GetLatestSubtitlesEnded != null) {
                        GetLatestSubtitlesEnded(this, new AsyncResponseArgs<IList<Subtitle>>(ex));
                    }
                }
            });
        }

        public void GetLatestSubtitlesAsync(string language) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetLatestSubtitles(language);
                    if (GetLatestSubtitlesEnded != null) {
                        GetLatestSubtitlesEnded(this, new AsyncResponseArgs<IList<Subtitle>>(result));
                    }
                } catch (Exception ex) {
                    if (GetLatestSubtitlesEnded != null) {
                        GetLatestSubtitlesEnded(this, new AsyncResponseArgs<IList<Subtitle>>(ex));
                    }
                }
            });
        }

        public void GetLatestSubtitlesAsync(string language, uint number) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetLatestSubtitles(language, number);
                    if (GetLatestSubtitlesEnded != null) {
                        GetLatestSubtitlesEnded(this, new AsyncResponseArgs<IList<Subtitle>>(result));
                    }
                } catch (Exception ex) {
                    if (GetLatestSubtitlesEnded != null) {
                        GetLatestSubtitlesEnded(this, new AsyncResponseArgs<IList<Subtitle>>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<IList<Subtitle>> GetLatestSubtitlesEnded;

        public void GetLatestShowSubtitlesAsync(string showUrl) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetLatestShowSubtitles(showUrl);
                    if (GetLatestShowSubtitlesEnded != null) {
                        GetLatestShowSubtitlesEnded(this, new AsyncResponseArgs<IList<Subtitle>>(result));
                    }
                } catch (Exception ex) {
                    if (GetLatestShowSubtitlesEnded != null) {
                        GetLatestShowSubtitlesEnded(this, new AsyncResponseArgs<IList<Subtitle>>(ex));
                    }
                }
            });
        }

        public void GetLatestShowSubtitlesAsync(string showUrl, string language) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetLatestShowSubtitles(showUrl, language);
                    if (GetLatestShowSubtitlesEnded != null) {
                        GetLatestShowSubtitlesEnded(this, new AsyncResponseArgs<IList<Subtitle>>(result));
                    }
                } catch (Exception ex) {
                    if (GetLatestShowSubtitlesEnded != null) {
                        GetLatestShowSubtitlesEnded(this, new AsyncResponseArgs<IList<Subtitle>>(ex));
                    }
                }
            });
        }

        public void GetLatestShowSubtitlesAsync(string showUrl, string language, uint number) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetLatestShowSubtitles(showUrl, language, number);
                    if (GetLatestShowSubtitlesEnded != null) {
                        GetLatestShowSubtitlesEnded(this, new AsyncResponseArgs<IList<Subtitle>>(result));
                    }
                } catch (Exception ex) {
                    if (GetLatestShowSubtitlesEnded != null) {
                        GetLatestShowSubtitlesEnded(this, new AsyncResponseArgs<IList<Subtitle>>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<IList<Subtitle>> GetLatestShowSubtitlesEnded;

        public void GetPlanningAsync() {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetPlanning();
                    if (GetPlanningEnded != null) {
                        GetPlanningEnded(this, new AsyncResponseArgs<IList<Episode>>(result));
                    }
                } catch (Exception ex) {
                    if (GetPlanningEnded != null) {
                        GetPlanningEnded(this, new AsyncResponseArgs<IList<Episode>>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<IList<Episode>> GetPlanningEnded;

        public void GetMembersPlanningAsync(string username) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetMembersPlanning(username);
                    if (GetMembersPlanningEnded != null) {
                        GetMembersPlanningEnded(this, new AsyncResponseArgs<IList<Episode>>(result));
                    }
                } catch (Exception ex) {
                    if (GetMembersPlanningEnded != null) {
                        GetMembersPlanningEnded(this, new AsyncResponseArgs<IList<Episode>>(ex));
                    }
                }
            });
        }

        public void GetMembersPlanningAsync(string username, bool unseenOnly) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetMembersPlanning(username, unseenOnly);
                    if (GetMembersPlanningEnded != null) {
                        GetMembersPlanningEnded(this, new AsyncResponseArgs<IList<Episode>>(result));
                    }
                } catch (Exception ex) {
                    if (GetMembersPlanningEnded != null) {
                        GetMembersPlanningEnded(this, new AsyncResponseArgs<IList<Episode>>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<IList<Episode>> GetMembersPlanningEnded;

        public void AuthenticateAsync(string username, string password) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = Authenticate(username, password);
                    if (AuthenticateEnded != null) {
                        AuthenticateEnded(this, new AsyncResponseArgs<string>(result));
                    }
                } catch (Exception ex) {
                    if (AuthenticateEnded != null) {
                        AuthenticateEnded(this, new AsyncResponseArgs<string>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<string> AuthenticateEnded;

        public void GetIsSessionActiveAsync() {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetIsSessionActive();
                    if (GetIsSessionActiveEnded != null) {
                        GetIsSessionActiveEnded(this, new AsyncResponseArgs<bool>(result));
                    }
                } catch (Exception ex) {
                    if (GetIsSessionActiveEnded != null) {
                        GetIsSessionActiveEnded(this, new AsyncResponseArgs<bool>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<bool> GetIsSessionActiveEnded;

        public void LogoffAsync() {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    Logoff();
                    if (LogoffEnded != null) {
                        LogoffEnded(this, new AsyncResponseArgs(true));
                    }
                } catch (Exception ex) {
                    if (LogoffEnded != null) {
                        LogoffEnded(this, new AsyncResponseArgs(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler LogoffEnded;

        public void GetMemberAsync(string username) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetMember(username);
                    if (GetMemberEnded != null) {
                        GetMemberEnded(this, new AsyncResponseArgs<Member>(result));
                    }
                } catch (Exception ex) {
                    if (GetMemberEnded != null) {
                        GetMemberEnded(this, new AsyncResponseArgs<Member>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<Member> GetMemberEnded;

        public void GetMembersNextEpisodesAsync(bool onlyNextEpisode) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetMembersNextEpisodes(onlyNextEpisode);
                    if (GetMembersNextEpisodesEnded != null) {
                        GetMembersNextEpisodesEnded(this, new AsyncResponseArgs<IList<Episode>>(result));
                    }
                } catch (Exception ex) {
                    if (GetMembersNextEpisodesEnded != null) {
                        GetMembersNextEpisodesEnded(this, new AsyncResponseArgs<IList<Episode>>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<IList<Episode>> GetMembersNextEpisodesEnded;

        public void GetMembersNextShowEpisodeAsync(bool onlyNextEpisode, string showUrl) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetMembersNextShowEpisode(onlyNextEpisode, showUrl);
                    if (GetMembersNextShowEpisodeEnded != null) {
                        GetMembersNextShowEpisodeEnded(this, new AsyncResponseArgs<Episode>(result));
                    }
                } catch (Exception ex) {
                    if (GetMembersNextShowEpisodeEnded != null) {
                        GetMembersNextShowEpisodeEnded(this, new AsyncResponseArgs<Episode>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<Episode> GetMembersNextShowEpisodeEnded;

        public void SetEpisodeAsSeenAsync(string showUrl, uint season, uint episode, ushort? mark) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    SetEpisodeAsSeen(showUrl, season, episode, mark);
                    if (SetEpisodeAsSeenEnded != null) {
                        SetEpisodeAsSeenEnded(this, new AsyncResponseArgs(true));
                    }
                } catch (Exception ex) {
                    if (SetEpisodeAsSeenEnded != null) {
                        SetEpisodeAsSeenEnded(this, new AsyncResponseArgs(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler SetEpisodeAsSeenEnded;

        public virtual void SetEpisodeAsDownloadedAsync(string showUrl, uint season, uint episode) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    SetEpisodeAsDownloaded(showUrl, season, episode);
                    if (SetEpisodeAsDownloadedEnded != null) {
                        SetEpisodeAsDownloadedEnded(this, new AsyncResponseArgs(true));
                    }
                } catch (Exception ex) {
                    if (SetEpisodeAsDownloadedEnded != null) {
                        SetEpisodeAsDownloadedEnded(this, new AsyncResponseArgs(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler SetEpisodeAsDownloadedEnded;

        public virtual void SetEpisodeMarkAsync(string showUrl, uint season, uint episode, ushort mark) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    SetEpisodeMark(showUrl, season, episode, mark);
                    if (SetEpisodeMarkEnded != null) {
                        SetEpisodeMarkEnded(this, new AsyncResponseArgs(true));
                    }
                } catch (Exception ex) {
                    if (SetEpisodeMarkEnded != null) {
                        SetEpisodeMarkEnded(this, new AsyncResponseArgs(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler SetEpisodeMarkEnded;

        public void GetNotificationsAsync(bool? seen, uint? count, uint? fromNotificationId) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetNotifications(seen, count, fromNotificationId);
                    if (GetNotificationsEnded != null) {
                        GetNotificationsEnded(this, new AsyncResponseArgs<IList<Notification>>(result));
                    }
                } catch (Exception ex) {
                    if (GetNotificationsEnded != null) {
                        GetNotificationsEnded(this, new AsyncResponseArgs<IList<Notification>>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<IList<Notification>> GetNotificationsEnded;

        public void SignupAsync(string username, string password, string email) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    Signup(username, password, email);
                    if (SignupEnded != null) {
                        SignupEnded(this, new AsyncResponseArgs(true));
                    }
                } catch (Exception ex) {
                    if (SignupEnded != null) {
                        SignupEnded(this, new AsyncResponseArgs(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler SignupEnded;

        public void GetFriendsAsync(string username) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetFriends(username);
                    if (GetFriendsEnded != null) {
                        GetFriendsEnded(this, new AsyncResponseArgs<IList<string>>(result));
                    }
                } catch (Exception ex) {
                    if (GetFriendsEnded != null) {
                        GetFriendsEnded(this, new AsyncResponseArgs<IList<string>>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<IList<string>> GetFriendsEnded;

        public void GetBadgesAsync(string username) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetBadges(username);
                    if (GetBadgesEnded != null) {
                        GetBadgesEnded(this, new AsyncResponseArgs<string[]>(result));
                    }
                } catch (Exception ex) {
                    if (GetBadgesEnded != null) {
                        GetBadgesEnded(this, new AsyncResponseArgs<string[]>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<string[]> GetBadgesEnded;

        public void GetCommentsForShowAsync(string showUrl) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetCommentsForShow(showUrl);
                    if (GetCommentsForShowEnded != null) {
                        GetCommentsForShowEnded(this, new AsyncResponseArgs<IList<Comment>>(result));
                    }
                } catch (Exception ex) {
                    if (GetCommentsForShowEnded != null) {
                        GetCommentsForShowEnded(this, new AsyncResponseArgs<IList<Comment>>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<IList<Comment>> GetCommentsForShowEnded;

        public void GetCommentsForEpisodeAsync(string showUrl, uint season, uint episode) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetCommentsForEpisode(showUrl, season, episode);
                    if (GetCommentsForEpisodeEnded != null) {
                        GetCommentsForEpisodeEnded(this, new AsyncResponseArgs<IList<Comment>>(result));
                    }
                } catch (Exception ex) {
                    if (GetCommentsForEpisodeEnded != null) {
                        GetCommentsForEpisodeEnded(this, new AsyncResponseArgs<IList<Comment>>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<IList<Comment>> GetCommentsForEpisodeEnded;

        public void GetCommentsForMemberAsync(string username) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetCommentsForMember(username);
                    if (GetCommentsForMemberEnded != null) {
                        GetCommentsForMemberEnded(this, new AsyncResponseArgs<IList<Comment>>(result));
                    }
                } catch (Exception ex) {
                    if (GetCommentsForMemberEnded != null) {
                        GetCommentsForMemberEnded(this, new AsyncResponseArgs<IList<Comment>>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<IList<Comment>> GetCommentsForMemberEnded;

        public void CommentShowAsync(string showUrl, string comment, uint? inReplyTo) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    CommentShow(showUrl, comment, inReplyTo);
                    if (CommentShowEnded != null) {
                        CommentShowEnded(this, new AsyncResponseArgs(true));
                    }
                } catch (Exception ex) {
                    if (CommentShowEnded != null) {
                        CommentShowEnded(this, new AsyncResponseArgs(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler CommentShowEnded;

        public void CommentEpisodeAsync(string showUrl, uint season, uint episode, string comment, uint? inReplyTo) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    CommentEpisode(showUrl, season, episode, comment, inReplyTo);
                    if (CommentEpisodeEnded != null) {
                        CommentEpisodeEnded(this, new AsyncResponseArgs(true));
                    }
                } catch (Exception ex) {
                    if (CommentEpisodeEnded != null) {
                        CommentEpisodeEnded(this, new AsyncResponseArgs(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler CommentEpisodeEnded;

        public void CommentMemberAsync(string username, string comment, uint? inReplyTo) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    CommentMember(username, comment, inReplyTo);
                    if (CommentMemberEnded != null) {
                        CommentMemberEnded(this, new AsyncResponseArgs(true));
                    }
                } catch (Exception ex) {
                    if (CommentMemberEnded != null) {
                        CommentMemberEnded(this, new AsyncResponseArgs(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler CommentMemberEnded;

        public void GetMainTimelineAsync(uint? count) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetMainTimeline(count);
                    if (GetMainTimelineEnded != null) {
                        GetMainTimelineEnded(this, new AsyncResponseArgs<IList<TimelineItem>>(result));
                    }
                } catch (Exception ex) {
                    if (GetMainTimelineEnded != null) {
                        GetMainTimelineEnded(this, new AsyncResponseArgs<IList<TimelineItem>>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<IList<TimelineItem>> GetMainTimelineEnded;

        public void GetFriendsTimelineAsync(uint? count) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetFriendsTimeline(count);
                    if (GetFriendsTimelineEnded != null) {
                        GetFriendsTimelineEnded(this, new AsyncResponseArgs<IList<TimelineItem>>(result));
                    }
                } catch (Exception ex) {
                    if (GetFriendsTimelineEnded != null) {
                        GetFriendsTimelineEnded(this, new AsyncResponseArgs<IList<TimelineItem>>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<IList<TimelineItem>> GetFriendsTimelineEnded;

        public void GetMemberTimelineAsync(string username, uint? count) {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetMemberTimeline(username, count);
                    if (GetMemberTimelineEnded != null) {
                        GetMemberTimelineEnded(this, new AsyncResponseArgs<IList<TimelineItem>>(result));
                    }
                } catch (Exception ex) {
                    if (GetMemberTimelineEnded != null) {
                        GetMemberTimelineEnded(this, new AsyncResponseArgs<IList<TimelineItem>>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<IList<TimelineItem>> GetMemberTimelineEnded;

        public void GetStatusAsync() {
            ThreadPool.QueueUserWorkItem(param => {
                try {
                    var result = GetStatus();
                    if (GetStatusEnded != null) {
                        GetStatusEnded(this, new AsyncResponseArgs<ApiStatus>(result));
                    }
                } catch (Exception ex) {
                    if (GetStatusEnded != null) {
                        GetStatusEnded(this, new AsyncResponseArgs<ApiStatus>(ex));
                    }
                }
            });
        }

        public event AsyncResponseHandler<ApiStatus> GetStatusEnded;

        #endregion
#pragma warning restore 1591

        #region Async tools

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Disposing this object when no more needed will prevent memory leaks.
        /// </summary>
        /// <remarks>
        /// When overriding, don't forget to call the base method.
        /// </remarks>
        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing) {
            if (disposing) {
                if (factory != null) {
                    factory.SessionTokenChangedEvent -= factory_SessionTokenChangedEvent;
                    factory = null;
                }
                ClearEventHandlers();
            }
        }

        /// <summary>
        /// Set all event handlers to null. 
        /// </summary>
        /// <remarks>
        /// Using reflection is simplier because there are too much event handlers. 
        /// T4 would help.
        /// </remarks>
        private void ClearEventHandlers() {
            BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
            EventInfo[] events = this.GetType().GetEvents(flags);
            if (events == null)
                return;
            if (events.Length < 1)
                return;

            System.Collections.Hashtable ht = new System.Collections.Hashtable();

            for (int i = 0; i < events.Length; i++) {
                FieldInfo[] fields = events[i].DeclaringType.GetFields(flags);
                foreach (FieldInfo fi in fields) {
                    if (events[i].Name.Equals(fi.Name) && !ht.Contains(fi.Name))
                        ht.Add(fi.Name, fi);
                }
            }

            //TODO: finish that
        }

        #endregion

        protected static string HashString(string value) {
            byte[] data = System.Text.Encoding.UTF8.GetBytes(value);
            
            using (System.Security.Cryptography.MD5CryptoServiceProvider x =
                new System.Security.Cryptography.MD5CryptoServiceProvider()) {
                data = x.ComputeHash(data);
            }

            string ret = "";
            for (int i = 0; i < data.Length; i++)
                ret += data[i].ToString("x2").ToLower();
            return ret;
        }

    }
}
